/* This software was developed at the National Institute of Standards and
 * Technology by employees of the Federal Government in the course of
 * their official duties. Pursuant to title 17 Section 105 of the United
 * States Code this software is not subject to copyright protection and
 * is in the public domain.
 * NIST assumes no responsibility whatsoever for its use by other parties,
 * and makes no guarantees, expressed or implied, about its quality,
 * reliability, or any other characteristic.
 * <BR>
 * We would appreciate acknowledgement if the software is used.
 * <BR>
 * NIST ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" CONDITION AND
 * DISCLAIM ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER RESULTING
 * FROM THE USE OF THIS SOFTWARE.
 * </PRE></P>
 * @author  rouil
 */

#include "wimaxscheduler.h"
//#define FLOW_DEBUG
/*
 * Create a scheduler
 * @param mac The Mac where it is located
 */
WimaxScheduler::WimaxScheduler() :data_packet_number_(0) {
}

/*
 * Set the mac
 * @param mac The Mac where it is located
 */
void WimaxScheduler::setMac(Mac802_16 *mac) {
    assert(mac != NULL);

    mac_ = mac;
}

/**
 * Initialize the scheduler.
 */
void WimaxScheduler::init() {

}

/**
 * This function is used to schedule bursts/packets
 */
void WimaxScheduler::schedule() {
    //defined by subclasses
}

/**
 * @todo: estimateBw needs to be implemented, Chris
 */

int WimaxScheduler::estimateBW(flow_record *flowRecord, Connection *c)
{

#ifdef FLOW_DEBUGsa
    printf("last_granted_time = %f, last_current_time = %f\n", flowRecord->last_granted_time, flowRecord->last_current_time);
#endif
    if (flowRecord->estimateBWcalled == 0)
        flowRecord->startTime = NOW;

    if (flowRecord->estimateBWcalled >= 2)
    {
        flowRecord->last_time_width = flowRecord->last_granted_time - flowRecord->last_current_time; //compute last time width
        if (flowRecord->last_time_width != 0)
            flowRecord->last_arrival_rate = (flowRecord->queue_last_granted_time - flowRecord->queue_last_current_time) / flowRecord->last_time_width; //compute last arrival rate


        flowRecord->last_time_width_error = flowRecord->last_est_time_width - flowRecord->last_time_width; //compute last time width error
        flowRecord->last_arrival_rate_error = flowRecord->last_est_arrival_rate - flowRecord->last_arrival_rate; //compute last arrival rate error

        flowRecord->est_time_width = (flowRecord->last_last_time_width + flowRecord->last_time_width) / 2;
        flowRecord->est_arrival_rate = (flowRecord->last_last_arrival_rate - flowRecord->last_arrival_rate) - flowRecord->last_arrival_rate_error;
    }

#ifdef FLOW_DEBUG
    printf("the %d time being called\n", flowRecord->estimateBWcalled);
    printf("last_time_width = %f, last_time_width_error = %f\n", flowRecord->last_time_width, flowRecord->last_time_width_error);
    printf("queue_last_granted_time = %d, queue_last_current_time = %d\n", flowRecord->queue_last_granted_time, flowRecord->queue_last_current_time);
    printf("last_arrival_rate = %f, last_arrival_rate_error = %f\n\n", flowRecord->last_arrival_rate, flowRecord->last_arrival_rate_error);
#endif

#ifdef FLOW_DEBUG
    printf("last_last_time_width = %f, last_time_width = %f\n", flowRecord->last_last_time_width, flowRecord->last_time_width);
    printf("last_last_arrival_rate = %f, last_arrival_rate = %f\n", flowRecord->last_last_arrival_rate, flowRecord->last_arrival_rate);
#endif

    flowRecord->last_last_time_width = flowRecord->last_time_width; //set the last last time width to the last time width
    flowRecord->last_est_time_width = flowRecord->est_time_width; //set the last estimate time width to the current estimate time width

    flowRecord->last_last_arrival_rate = flowRecord->last_arrival_rate; //set the last last arrival rate to the last arrival rate
    flowRecord->last_est_arrival_rate = flowRecord->est_arrival_rate; //set the last estimate arrival rate to the current estimate arrival rate

    flowRecord->est_offset = (int)round(flowRecord->est_arrival_rate * flowRecord->est_time_width);

#ifdef FLOW_DEBUG
    printf("estimate arrival rate = %f, time width = %f, offset = %d\n", flowRecord->est_arrival_rate, flowRecord->est_time_width, flowRecord->est_offset);
#endif

    flowRecord->queue_last_current_time = c->queueByteLength(); //set the queue at last current time to the queue right now
    flowRecord->last_current_time = NOW; //set the last current time to the current time
    flowRecord->last_granted_time = 0; //set the last_granted time to zero

    flowRecord->estimateBWcalled++;
    return flowRecord->est_offset;
}

/**
 * Transfer the packets from the given connection to the given burst
 * @param con The connection
 * @param b The burst
 * @param b_data Amount of data in the burst
 * @return the new burst occupation
 */
int WimaxScheduler::transfer_packets(Connection *c, Burst *b, int b_data) {
    int packet_number = 1;
    Packet *p;
    hdr_cmn* ch;
    hdr_mac802_16 *wimaxHdr;
    double txtime, txtime2;
    int txtime_s;
    bool pkt_transfered = false;
    OFDMPhy *phy = mac_->getPhy();
    flow_record *flowRecord;
    if (mac_->findFlowRecord(c) != -1 && mac_->getNodeType() == STA_MN)
        flowRecord = &(mac_->UpflowTable[mac_->findFlowRecord(c)]);
    else
        flowRecord = NULL;

    p = c->get_queue()->head();

    int max_data;
    if (mac_->getNodeType() == STA_BS)
        max_data = phy->getMaxPktSize(b->getDuration(), mac_->getMap()->getDlSubframe()->getProfile(b->getIUC())->getEncoding()) - b_data;
    else
        max_data = phy->getMaxPktSize(b->getDuration(), mac_->getMap()->getUlSubframe()->getProfile(b->getIUC())->getEncoding()) - b_data;
    debug2("In Mac %d max data=%d (burst duration=%d, b_data=%d)\n", mac_->addr(), max_data, b->getDuration(), b_data);

    if (flowRecord && flowRecord->qosRecord->getScheduling() == SERVICE_ertPS)
       max_data -= HDR_MAC802_16_PIGGYBACKSUB_SIZE; //reserved bytes for piggyback request header

    if (max_data < HDR_MAC802_16_SIZE ||
            (c->getFragmentationStatus() != FRAG_NOFRAG && max_data < HDR_MAC802_16_SIZE + HDR_MAC802_16_FRAGSUB_SIZE))
        return b_data; //not even space for header;which means that the left space is too small

    //start to process packets if there's still space

    if(flowRecord)
    {
        if (packet_number == 1 && flowRecord->qosRecord->getScheduling() == SERVICE_rtPS)
        {
            //printf("\033[1;32mhere at sending data, time right now is %f, queue right now is %d\033[m\n", NOW, c->queueByteLength());
            flowRecord->last_granted_time = NOW;
            flowRecord->queue_last_granted_time = c->queueByteLength();
        }
    }
    //printf("max_data = %d, queue right now = %d\n", max_data, c->get_queue()->byteLength());
    //if(NOW > 19.39475f)
    //    printf("ccc\n");
    while (p) {
        ch = HDR_CMN(p);
        wimaxHdr = HDR_MAC802_16(p);
        if (mac_->getNodeType() == STA_BS)
            max_data = phy->getMaxPktSize(b->getDuration(), mac_->getMap()->getDlSubframe()->getProfile(b->getIUC())->getEncoding()) - b_data;
        else
            max_data = phy->getMaxPktSize(b->getDuration(), mac_->getMap()->getUlSubframe()->getProfile(b->getIUC())->getEncoding()) - b_data;

        //printf("   %lf max_data = %d, b_data = %d, queue right now = %d\n", NOW, max_data, b_data, c->get_queue()->byteLength());

        if (flowRecord && flowRecord->qosRecord->getScheduling() == SERVICE_ertPS && packet_number == 1)
        {
            max_data -= HDR_MAC802_16_PIGGYBACKSUB_SIZE; //reserved bytes for piggyback request header
            flowRecord->last_granted_time = NOW;
            flowRecord->queue_last_granted_time = c->get_queue()->byteLength();
        }
        debug2("\tIn Mac %d max data=%d (burst duration=%d, b_data=%d)\n", mac_->addr(), max_data, b->getDuration(), b_data);
        if (max_data < HDR_MAC802_16_SIZE ||
                (c->getFragmentationStatus() != FRAG_NOFRAG && max_data < HDR_MAC802_16_SIZE + HDR_MAC802_16_FRAGSUB_SIZE))
        {
            if (flowRecord && flowRecord->qosRecord->getScheduling() == SERVICE_ertPS)
            {
                wimaxHdr->header.esf = 1;
                //int offset = estimateBW(flowRecord, c);
                if ((c->get_queue()->byteLength()) <= 0x7FF)
                    wimaxHdr->piggyback_subheader.e_piggyback_req = c->queueByteLength();
                else
                    wimaxHdr->piggyback_subheader.e_piggyback_req = 0x7FF;
                //printf("not yet done, qos = %d, queue length = %d, requested = %d\n", flowRecord->qosRecord->getScheduling(), c->get_queue()->byteLength(), wimaxHdr->piggyback_subheader.e_piggyback_req);
            }
            return b_data; //not even space for header
        }
        if (c->getFragmentationStatus() != FRAG_NOFRAG) {
            if (max_data >= ch->size() - c->getFragmentBytes() + HDR_MAC802_16_FRAGSUB_SIZE) {
                //add fragmentation header
                wimaxHdr->header.type_frag = true;
                //no need to fragment again
                wimaxHdr->frag_subheader.fc = FRAG_LAST;
                wimaxHdr->frag_subheader.fsn = c->getFragmentNumber();
                //remove packet from queue
                c->dequeue();
                ch->size() = ch->size() - c->getFragmentBytes() + HDR_MAC802_16_FRAGSUB_SIZE; //new packet size
                //update fragmentation
                debug2("End of fragmentation %d (max_data=%d, bytes to send=%d\n", wimaxHdr->frag_subheader.fsn, max_data, ch->size());
                c->updateFragmentation(FRAG_NOFRAG, 0, 0);
            } else {
                //need to fragment the packet again
                p = p->copy(); //copy packet to send
                ch = HDR_CMN(p);
                wimaxHdr = HDR_MAC802_16(p);
                //add fragmentation header
                wimaxHdr->header.type_frag = true;
                wimaxHdr->frag_subheader.fc = FRAG_CONT;
                wimaxHdr->frag_subheader.fsn = c->getFragmentNumber();
                ch->size() = max_data; //new packet size
                //update fragmentation
                c->updateFragmentation(FRAG_CONT, (c->getFragmentNumber() + 1) % 8, c->getFragmentBytes() + max_data - (HDR_MAC802_16_SIZE + HDR_MAC802_16_FRAGSUB_SIZE));
                debug2("Continue fragmentation %d\n", wimaxHdr->frag_subheader.fsn);
            }
        } else { //Fragmentation status == NO_FRAG
            if (max_data < ch->size() && c->isFragEnable()) {
                //need to fragment the packet for the first time
                p = p->copy(); //copy packet to send
                ch = HDR_CMN(p);
                wimaxHdr = HDR_MAC802_16(p);
                //add fragmentation header
                wimaxHdr->header.type_frag = true;
                wimaxHdr->frag_subheader.fc = FRAG_FIRST;
                wimaxHdr->frag_subheader.fsn = c->getFragmentNumber();
                ch->size() = max_data; //new packet size
                //update fragmentation
                c->updateFragmentation(FRAG_FIRST, 1, c->getFragmentBytes() + max_data - (HDR_MAC802_16_SIZE + HDR_MAC802_16_FRAGSUB_SIZE));
                debug2("First fragmentation\n");
            } else if (max_data < ch->size() && !c->isFragEnable()) {
                //the connection does not support fragmentation
                //can't move packets anymore
                return b_data;
            } else {
                //no fragmentation necessary
                c->dequeue();
            }
        }

        if (mac_->getNodeType() == STA_BS) {
            txtime = phy->getTrxTime(ch->size(), mac_->getMap()->getDlSubframe()->getProfile(b->getIUC())->getEncoding());
            txtime2 = phy->getTrxSymbolTime(b_data + ch->size(), mac_->getMap()->getDlSubframe()->getProfile(b->getIUC())->getEncoding());
        } else {
            txtime = phy->getTrxTime(ch->size(), mac_->getMap()->getUlSubframe()->getProfile(b->getIUC())->getEncoding());
            txtime2 = phy->getTrxSymbolTime(b_data + ch->size(), mac_->getMap()->getUlSubframe()->getProfile(b->getIUC())->getEncoding());
        }
        ch->txtime() = txtime;
        txtime_s = (int) round(txtime2 / phy->getSymbolTime()); //in units of symbol
        //printf ("symbtime=%f\n", phy->getSymbolTime ());
        //printf ("Check packet to send: size=%d txtime=%f(%d) duration=%d(%f)\n", ch->size(),txtime, txtime_s, b->getDuration(), b->getDuration()*phy->getSymbolTime ());
        assert(txtime2 <= b->getDuration() * phy->getSymbolTime());
        //printf ("transfert to burst (txtime=%f, txtime2=%f, bduration=%f)\n", txtime,txtime2,b->getDuration()*phy->getSymbolTime ());
        //p = c->dequeue();   //dequeue connection queue
        //@todo need to implement bandwidth request estimate of ertPS, Chris(remove debug message by ix)
            data_packet_number_++;
            //@todo : change packet_number for debug (by ix)
            //HDR_CMN(p)->number_ = flowRecord->number;
            HDR_CMN(p)->number_ = data_packet_number_;

//            if (wimaxHdr->piggyback_subheader.e_piggyback_req == 0)
//                printf("At %f burst %d packet # %d QoS_type %d packet_type %d transferred\n"  \
//                       ,NOW ,flowRecord->burst, HDR_CMN(p)->number_, flowRecord->qosRecord->getScheduling(), HDR_CMN(p)->ptype_);
//            else
//                printf("At %f burst %d packet # %d QoS_type %d packet_type %d transferred\033[1;37m (req_len = %d)\033[0m\n"  \
//                       ,NOW ,flowRecord->burst, HDR_CMN(p)->number_, flowRecord->qosRecord->getScheduling(), HDR_CMN(p)->ptype_ , wimaxHdr->piggyback_subheader.e_piggyback_req);
        b->enqueue(p); //enqueue into burst
        b_data += ch->size(); //increment amount of data enqueued
        if (!pkt_transfered && mac_->getNodeType() != STA_BS) { //if we transfert at least one packet, remove bw request
            pkt_transfered = true;
            mac_->getMap()->getUlSubframe()->getBw_req()->removeRequest(c->get_cid());
        }
        p = c->get_queue()->head(); //get new head
        packet_number++;
        if (flowRecord && c->get_queue()->byteLength() == 0 && flowRecord->qosRecord->getScheduling() == SERVICE_ertPS)
        {
            wimaxHdr->header.esf = 1;
            //int offset = estimateBW(flowRecord, c);
            if ((c->queueByteLength() / 4) <= 0x7FF)
                wimaxHdr->piggyback_subheader.e_piggyback_req = c->queueByteLength()/4;
            else
                wimaxHdr->piggyback_subheader.e_piggyback_req = 0x7FF;
            //printf("done, qos = %d, queue length = %d, requested = %d\n", flowRecord->qosRecord->getScheduling(), c->get_queue()->byteLength(), wimaxHdr->piggyback_subheader.e_piggyback_req);
        }
    }
    return b_data;
}


